﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq.Expressions;
using System.Reflection;

namespace Common
{

    /// <summary>
    /// 读取字段或属性的值
    /// </summary>
    public class PropertyFieldReader
    {
        static ConcurrentDictionary<FieldInfo, Delegate> _getFieldValueDlgCache = new ConcurrentDictionary<FieldInfo, Delegate>();
        static ConcurrentDictionary<PropertyInfo, Delegate> _getPropertyValueDlgCache = new ConcurrentDictionary<PropertyInfo, Delegate>();
        static ConcurrentDictionary<Type, Delegate> _getFieldValueDlgCache2 = new ConcurrentDictionary<Type, Delegate>();
        static ConcurrentDictionary<Type, Delegate> _getPropertyValueDlgCache2 = new ConcurrentDictionary<Type, Delegate>();

        /// <summary>
        /// 获取指定字段的值
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="obj"></param>
        /// <param name="fieldName"></param>
        /// <returns></returns>
        public static TValue GetFieldValue<TSource, TValue>(TSource obj, string fieldName)
        {
            Type type = typeof(TSource);
            FieldInfo fi = type.GetField(fieldName);
            if (fi == null)
                return default(TValue);
            else
            {
                var func = GetGetFieldValueDelegate<TSource, TValue>(fi);
                return func(obj);
            }
        }

        /// <summary>
        /// 获取 获取指定字段值 的委托
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="fieldInfo"></param>
        /// <returns></returns>
        public static Func<TSource, TValue> GetGetFieldValueDelegate<TSource, TValue>(FieldInfo fieldInfo)
        {
            Contract.Requires(fieldInfo != null);
            Delegate dlg;
            if (_getFieldValueDlgCache.TryGetValue(fieldInfo, out dlg))
            {
                return dlg as Func<TSource, TValue>;
            }
            else
            {

                Type fieldDeclaringType = fieldInfo.DeclaringType;

                ParameterExpression sourceParameter =
                 Expression.Parameter(typeof(TSource), "source");


                Expression sourceExpression = GetCastOrConvertExpression(
                    sourceParameter, fieldDeclaringType);

                MemberExpression fieldExpression = Expression.Field(sourceExpression, fieldInfo);

                Expression resultExpression = GetCastOrConvertExpression(
                fieldExpression, typeof(TValue));

                LambdaExpression lambda =
                    Expression.Lambda(typeof(Func<TSource, TValue>), resultExpression, sourceParameter);

                Func<TSource, TValue> compiled = (Func<TSource, TValue>)lambda.Compile();

                _getFieldValueDlgCache.TryAdd(fieldInfo, compiled);
                return compiled;
            }
        }
        
        /// <summary>
        /// 获取指定属性的值
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="obj"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public static TValue GetPropertyValue<TSource, TValue>(TSource obj, string propertyName)
        {
            PropertyInfo pi = typeof(TSource).GetProperty(propertyName);
            if (pi == null)
                return default(TValue);
            else
            {
                Func<TSource, TValue> dlg = GetGetPropertyValueDelegate<TSource, TValue>(pi);
                return dlg(obj);
            }
        }
        
        /// <summary>
        /// 获取 获取指定属性值 的委托
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="propertyInfo"></param>
        /// <returns></returns>
        public static Func<TSource, TValue> GetGetPropertyValueDelegate<TSource, TValue>(PropertyInfo propertyInfo)
        {
            Contract.Requires(propertyInfo != null);
            Delegate dlg;
            if (_getPropertyValueDlgCache.TryGetValue(propertyInfo, out dlg))
            {
                return dlg as Func<TSource, TValue>;
            }
            else
            {
                Func<TSource, TValue> rtn = (Func<TSource, TValue>)Delegate.CreateDelegate(typeof(Func<TSource, TValue>), propertyInfo.GetGetMethod());
                _getPropertyValueDlgCache.TryAdd(propertyInfo, rtn);
                return rtn;
            }
        }
        
        /// <summary>
        /// 获取指定属性的值
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <param name="obj"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public static object GetPropertyValue<TSource>(TSource obj, string propertyName)
        {

            PropertyInfo propertyInfo = typeof(TSource).GetProperty(propertyName);
            if (propertyInfo == null)
                return null;
            else
            {
                return GetGetPropertyValueDelegate<TSource>()(obj, propertyInfo.Name);
            }
        }
        
        /// <summary>
        /// 获取 获取指定属性值 的委托
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static Func<T, string, object> GetGetPropertyValueDelegate<T>()
        {
            Delegate dlg;
            if (_getPropertyValueDlgCache2.TryGetValue(typeof(T), out dlg))
            {
                return dlg as Func<T, string, object>;
            }
            else
            {
                Func<T, string, object> rtn = GenerateGetPropertyValue<T>();
                _getPropertyValueDlgCache2.TryAdd(typeof(T), rtn);
                return rtn;
            }
        }
       
        /// <summary>
        /// 生成 获取指定属性值 的委托
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static Func<T, string, object> GenerateGetPropertyValue<T>()
        {
            var type = typeof(T);
            var instance = Expression.Parameter(typeof(T), "instance");
            var srouceExp = GetCastOrConvertExpression(instance, type);
            var memberName = Expression.Parameter(typeof(string), "memberName");
            var nameHash = Expression.Variable(typeof(int), "nameHash");
            var calHash = Expression.Assign(nameHash, Expression.Call(memberName, typeof(object).GetMethod("GetHashCode")));
            var cases = new List<SwitchCase>();
            foreach (var propertyInfo in type.GetProperties())
            {
                //var property = Expression.Property(Expression.Convert(instance, typeof(T)), propertyInfo.Name);
                //var p = Expression.Convert(property, typeof(object));
                var propertyExpression = Expression.Property(srouceExp, propertyInfo);
                Expression lambda;
                if (propertyInfo.PropertyType.IsValueType && !IsNullableType(propertyInfo.PropertyType))
                {
                    lambda = Expression.Convert(propertyExpression, typeof(object));
                }
                else
                {
                    lambda = Expression.TypeAs(propertyExpression, typeof(object));
                }
                var propertyHash = Expression.Constant(propertyInfo.Name.GetHashCode(), typeof(int));

                cases.Add(Expression.SwitchCase(lambda, propertyHash));
            }
            var switchEx = Expression.Switch(nameHash, Expression.Constant(null), cases.ToArray());
            var methodBody = Expression.Block(typeof(object), new[] { nameHash }, calHash, switchEx);

            return Expression.Lambda<Func<T, string, object>>(methodBody, instance, memberName).Compile();
        }
        
        /// <summary>
        /// 获取指定字段的值
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <param name="obj"></param>
        /// <param name="fieldName"></param>
        /// <returns></returns>
        public static object GetFieldValue<TSource>(TSource obj, string fieldName)
        {
            FieldInfo fieldInfo = typeof(TSource).GetField(fieldName);
            if (fieldInfo == null)
                return null;
            else
            {
                return GetGetFieldValueDelegate<TSource>()(obj, fieldInfo.Name);
            }
        }
        
        /// <summary>
        /// 获取 获取指定字段值 的委托
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static Func<T, string, object> GetGetFieldValueDelegate<T>()
        {
            Delegate dlg;
            if (_getFieldValueDlgCache2.TryGetValue(typeof(T), out dlg))
            {
                return dlg as Func<T, string, object>;
            }
            else
            {
                Func<T, string, object> rtn = GenerateGetFieldValue<T>();
                _getFieldValueDlgCache2.TryAdd(typeof(T), rtn);
                return rtn;
            }
        }
        
        /// <summary>
        /// 生成 获取指定字段值 的委托
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static Func<T, string, object> GenerateGetFieldValue<T>()
        {
            var type = typeof(T);
            var instance = Expression.Parameter(typeof(T), "source");
            var srouceExp = GetCastOrConvertExpression(instance, type);
            var memberName = Expression.Parameter(typeof(string), "memberName");
            var nameHash = Expression.Variable(typeof(int), "nameHash");
            var calHash = Expression.Assign(nameHash, Expression.Call(memberName, typeof(object).GetMethod("GetHashCode")));
            var cases = new List<SwitchCase>();
            foreach (var fieldInfo in type.GetFields())
            {

                MemberExpression fieldExpression = Expression.Field(srouceExp, fieldInfo);

                Expression lambda;
                if (fieldInfo.FieldType.IsValueType && !IsNullableType(fieldInfo.FieldType))
                {
                    lambda = Expression.Convert(fieldExpression, typeof(object));
                }
                else
                {
                    lambda = Expression.TypeAs(fieldExpression, typeof(object));
                }

                //Expression resultExpression = GetCastOrConvertExpression(
                //fieldExpression, typeof(object));
                //var lambda = Expression.Field(Expression.Convert(instance, typeof(T)), fieldInfo.Name);


                //Type fieldDeclaringType = fieldInfo.DeclaringType;

                //ParameterExpression sourceParameter =
                // Expression.Parameter(typeof(T), "source");
                //Expression sourceExpression = GetCastOrConvertExpression(
                //    sourceParameter, fieldDeclaringType);

                //MemberExpression fieldExpression = Expression.Field(sourceExpression, fieldInfo);

                //Expression resultExpression = GetCastOrConvertExpression(
                //fieldExpression, fieldDeclaringType);

                //LambdaExpression lambda =
                //    Expression.Lambda(typeof(Func<T, object>), resultExpression, sourceParameter);



                var propertyHash = Expression.Constant(fieldInfo.Name.GetHashCode(), typeof(int));

                cases.Add(Expression.SwitchCase(lambda, propertyHash));
            }
            var switchEx = Expression.Switch(nameHash, Expression.Constant(null), cases.ToArray());
            var methodBody = Expression.Block(typeof(object), new[] { nameHash }, calHash, switchEx);

            return Expression.Lambda<Func<T, string, object>>(methodBody, instance, memberName).Compile();
        }
        
        /// <summary>
        /// 产生一个类型转换的表达式
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="targetType"></param>
        /// <returns></returns>
        private static Expression GetCastOrConvertExpression(Expression expression, Type targetType)
        {
            Expression result;
            Type expressionType = expression.Type;

            // Check if a cast or conversion is required.
            if (targetType.IsAssignableFrom(expressionType))
            {
                result = expression;
            }
            else
            {
                // Check if we can use the as operator for casting or if we must use the convert method
                if (targetType.IsValueType && !IsNullableType(targetType))
                {
                    result = Expression.Convert(expression, targetType);
                }
                else
                {
                    result = Expression.TypeAs(expression, targetType);
                }
            }

            return result;
        }

        /// <summary>
        /// 判断指定类型是否为一个可空类型
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private static bool IsNullableType(Type type)
        {
            if (type == null) throw new ArgumentNullException("type");

            bool result = false;
            if (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
            {
                result = true;
            }

            return result;
        }

    }

}
